當需要多個物件同時「監聽」一個相同物件,一旦該物件發生變化時,監聽的物件們將自動採取相對應的處理。
通常是發生在單個物件的狀態變化發生時,其他物件將會動態地跟著調整、執行各自對應的行為。
在模式內是這樣稱呼的:
作法是:
interface,使用 update() 作為共同方法。interface,會建立三個常用的方法:
add()。remove()。notify()。update() 的細節。notify() 要思考的是,通知者因為哪些行為改變自身狀態後才需要「通知」觀察者。以下範例以「簡易判斷 Log 等級後發出警示」為核心製作。
製作 Log 資料:Log
public class Log {
    private String title;
    private String level;
    private String message;
    private String type;
    public Log(String title, String level, String message, String type) {
        this.title = title;
        this.level = level;
        this.message = message;
        this.type = type;
    }
    public String getTitle() {
        return title;
    }
    public String getLevel() {
        return level;
    }
    public String getMessage() {
        return title + ", " + message;
    }
    public String getType() {
        return type;
    }
}
製作觀察者的虛擬層親代:Siren
public interface Siren {
    void update(String logLevel, String message);
}
製作通知者的虛擬層親代:LogCollectorMethods
public interface LogCollectorMethods {
    void addSubject(String subject);
    void addSubscriber(String subject, Siren siren);
    void removeSubscriber(String subject, Siren siren);
    void notifySubscribers(String subject, String level, String message);
}
製作觀察者子代:ApiPassFailedSiren、LogInFailedSiren(Observer 物件)
public class ApiPassFailedSiren implements Siren {
    private String name;
    private String level;
    public ApiPassFailedSiren(String name, String level) {
        this.name = name;
        this.level = level;
    }
    @Override
    public void update(String logLevel, String message) {
        if (level.equals("low")) {
            alert(message);
        } else if (level.equals("medium")) {
            if (logLevel.equals("high") || logLevel.equals("medium")) {
                alert(message);
            }
        } else if (level.equals("high") && logLevel.equals("high")) {
            alert(message);
        }
    }
    private void alert(String message) {
        System.out.println("The Siren: '" + name + "' is working now, and the message is:\n" + message);
    }
}
public class LogInFailedSiren implements Siren {
    private String name;
    private String level;
    public LogInFailedSiren(String name, String level) {
        this.name = name;
        this.level = level;
    }
    @Override
    public void update(String logLevel, String message) {
        if (level.equals("low")) {
            alert(message);
        } else if (level.equals("medium")) {
            if (logLevel.equals("high") || logLevel.equals("medium")) {
                alert(message);
            }
        } else if (level.equals("high") && logLevel.equals("high")) {
            alert(message);
        }
    }
    private void alert(String message) {
        System.out.println("The 'Log In Failed' " + name + " Siren is working now, and the message is:\n" + message);
    }
}
製作通知者子代:ApiPassFailedSiren(Subject 物件)
public class LogCollector implements LogCollectorMethods {
    private Map<String, List<Siren>> subscribers;
    public LogCollector() {
        this.subscribers = new HashMap<>();
    }
    @Override
    public void addSubject(String subject) {
        if (!subscribers.containsKey(subject)) {
            subscribers.put(subject, new ArrayList<>());
        }
    }
    @Override
    public void addSubscriber(String subject, Siren siren) {
        if (!subscribers.containsKey(subject)) {
            subscribers.put(subject, new ArrayList<>());
        }
        subscribers.get(subject).add(siren);
    }
    @Override
    public void removeSubscriber(String subject, Siren siren) {
        List<Siren> sirens = subscribers.get(subject);
        sirens.remove(siren);
    }
    @Override
    public void notifySubscribers(String subject, String level, String message) {
        List<Siren> sirens = subscribers.get(subject);
        for (Siren siren : sirens) {
            siren.update(level, message);
        }
        System.out.println("");
    }
    public void receiveLog(Log log) {
        notifySubscribers(log.getType(), log.getLevel(), log.getMessage());
    }
}
測試,建立通知者後,依序註冊觀察者,最後試著發送訊息:LogCollectorObserverSample
public class LogCollectorObserverSample {
    public static void main(String[] args) {
        LogCollector logCollector = new LogCollector();
        logCollector.addSubject("api");
        logCollector.addSubscriber("api", new ApiPassFailedSiren("/users", "low"));
        logCollector.addSubscriber("api", new ApiPassFailedSiren("/photos", "medium"));
        logCollector.addSubscriber("api", new ApiPassFailedSiren("/cars", "low"));
        logCollector.addSubscriber("api", new ApiPassFailedSiren("/keys", "high"));
        logCollector.addSubject("logIn");
        logCollector.addSubscriber("logIn", new LogInFailedSiren("users", "high"));
        logCollector.addSubscriber("logIn", new LogInFailedSiren("admin", "low"));
        logCollector.receiveLog(new Log("api pass failed", "high", "the client uses wrong token", "api"));
        logCollector.receiveLog(new Log("log in failed", "low", "the account is wrong", "logIn"));
    }
}
製作 Log 資料:Log
class Log {
  /**
   * @param {string} title
   * @param {string} level
   * @param {string} message
   * @param {string} type
   */
  constructor(title, level, message, type) {
    this.title = title;
    this.level = level;
    this.message = message;
    this.type = type;
  }
  getTitle() {
    return this.title;
  }
  getLevel() {
    return this.level;
  }
  getMessage() {
    return this.title + ", " + this.message;
  }
  getType() {
    return this.type;
  }
}
製作觀察者的虛擬層親代:Siren
/** @abstract */
class Siren {
  /**
   * @param {string} name
   * @param {string} level
   */
  constructor(name, level) {
    this.name = name;
    this.level = level;
  }
  /**
 * @override
 * @param {string} logLevel
 * @param {string} message
 */
  update(logLevel, message) {
    if (this.level === "low") {
      this.alert(message);
    } else if (this.level === "medium") {
      if (logLevel === "high" || logLevel === "medium") {
        this.alert(message);
      }
    } else if (this.level === "high" && logLevel === "high") {
      this.alert(message);
    }
  }
  /** @param {string} message */
  alert(message) { return; }
  /** @returns {string} */
  getName() { return; }
}
製作通知者的虛擬層親代:LogCollectorMethods
/** @abstract */
class LogCollectorMethods {
  constructor() {
    /** @type {Map<String, Siren[]>} */
    this.subscribers = new Map();
  }
  /** @param {string} subject */
  addSubject(subject) { return; }
  /**
   * @param {string} subject
   * @param {Siren} siren
   */
  addSubscriber(subject, siren) { return; }
  /**
 * @param {string} subject
 * @param {Siren} siren
 */
  removeSubscriber(subject, siren) { return; }
  /**
   * @param {string} subject
   * @param {string} level
   * @param {string} message
   */
  notifySubscribers(subject, level, message) { return; }
}
製作觀察者子代:ApiPassFailedSiren、LogInFailedSiren(Observer 物件)
class ApiPassFailedSiren extends Siren {
  /**
   * @param {string} name
   * @param {string} level
   */
  constructor(name, level) {
    super(name, level);
  }
  /**
   * @override
   * @param {string} message
   */
  alert(message) {
    console.log("The Siren: '" + this.name + "' is working now, and the message is:\n" + message);
  }
  /** @override */
  getName() {
    return this.name;
  }
}
class LogInFailedSiren extends Siren {
  /**
   * @param {string} name
   * @param {string} level
   */
  constructor(name, level) {
    super(name, level);
  }
  /**
   * @override
   * @param {string} message
   */
  alert(message) {
    console.log("The 'Log In Failed' " + this.name + " Siren is working now, and the message is:\n" + message);
  }
  /** @override */
  getName() {
    return this.name;
  }
}
製作通知者子代:ApiPassFailedSiren(Subject 物件)
class LogCollector extends LogCollectorMethods {
  constructor() {
    super();
  }
  /**
   * @override
   * @param {string} subject
   */
  addSubject(subject) {
    if (!this.subscribers.has(subject)) {
      this.subscribers.set(subject, []);
    }
  }
  /**
   * @override
   * @param {string} subject
   * @param {Siren} siren
   */
  addSubscriber(subject, siren) {
    if (!this.subscribers.has(subject)) {
      this.subscribers.set(subject, []);
    }
    this.subscribers.get(subject).push(siren);
  }
  /**
   * @override
   * @param {string} subject
   * @param {Siren} siren
   */
  removeSubscriber(subject, siren) {
    const sirens = this.subscribers.get(subject);
    const removeSpecificSiren = sirens.filter(item => item.getName() !== siren.getName())
    this.subscribers.set(subject, removeSpecificSiren);
  }
  /**
   * @override
   * @param {string} subject
   * @param {string} level
   * @param {string} message
   */
  notifySubscribers(subject, level, message) {
    const sirens = this.subscribers.get(subject);
    for (const siren of sirens) {
      siren.update(level, message);
    }
    console.log("");
  }
  /** @param {Log} log */
  receiveLog(log) {
    this.notifySubscribers(log.getType(), log.getLevel(), log.getMessage());
  }
}
測試,建立通知者後,依序註冊觀察者,最後試著發送訊息:logCollectorObserverSample
const logCollectorObserverSample = () => {
  const logCollector = new LogCollector();
  logCollector.addSubject("api");
  logCollector.addSubscriber("api", new ApiPassFailedSiren("/users", "low"));
  logCollector.addSubscriber("api", new ApiPassFailedSiren("/photos", "medium"));
  logCollector.addSubscriber("api", new ApiPassFailedSiren("/cars", "low"));
  logCollector.addSubscriber("api", new ApiPassFailedSiren("/keys", "high"));
  logCollector.addSubject("logIn");
  logCollector.addSubscriber("logIn", new LogInFailedSiren("users", "high"));
  logCollector.addSubscriber("logIn", new LogInFailedSiren("admin", "low"));
  logCollector.receiveLog(new Log("api pass failed", "high", "the client uses wrong token", "api"));
  logCollector.receiveLog(new Log("log in failed", "low", "the account is wrong", "logIn"));
};
logCollectorObserverSample();
Observer 模式還有另一個稱呼:Publish/Subscribe,這稱呼直接了當地表達該模式的重點。
自己實作時觀察到:
delegate 可以簡化。最後要提一點,Observer 模式與 Mediator 模式,兩者的初衷不同,前者是建立單向的連結,後者是消除物件們複雜的依賴關係,而實作騎來卻十分相似,想想這或許是讓程式碼維持高彈性、減少複雜度等目的後,必然的結果也說不定。
明天將介紹 Behavioural patterns 的第八個模式:State 模式。